<!DOCTYPE HTML>
<html>
	<head>
		<!-- Set the character encoding -->
		<meta charset="UTF-8" />
		
		<!-- CSS -->
		<link type="text/css" rel="stylesheet" href="style.css" />
		
		<!-- Javascript -->
		<script type="text/javascript" src="scripts/protovis-r3.2.js"></script>
		<script type="text/javascript" src="scripts/centroid.js"></script>
		<script type="text/javascript" src="scripts/us_lowres.js"></script>
		<script type="text/javascript" src="scripts/popupDiv.js"></script>
		<script type="text/javascript" src="stateOccupationData.js"></script>
		<script type="text/javascript" src="scripts/titleDescription.js"></script>		
		
		<!-- Title -->
		<title>Geo Tree Map</title>
		
	</head>
	<body>
		<!-- Data on demand popup window -->
		<div id="popupData"></div>
		
		<div id="visTitle">Occupational Multimap</div>
		
		<!-- container div -->
		<div id="container">
		
		<!-- Left panel with titles -->
		<div id="leftPanel">
			<div id="geoTitle" class="mapTitle"><div class="line1"></div><div class="line2"></div><div class="line3"></div></div>
			<div id="treeTitle" class="mapTitle"><div class="line1"></div><div class="line2"></div><div class="line3"></div></div>
		</div>
		
		<!-- Protovis rendering -->
		<div id="fig">
			<script type="text/javascript">
			
var selectedOccupation = {
			category:"Computer and mathematical science occupations",
			subcategory:"Computer software engineers - applications"
		},
		selectedState = us_lowres[14],
		selectedMeasure = "total";

var width = 800,
		geoHeight = 400,
		treeHeight = 300,
		measureMaxVal = 0,
		measureMinVal = 0;
		
var geoScale;
var intervalScale;		
var geoColor;
var numBoxesInLegend = 5;

pv.Colors.category24 = function() {
	  var scale = pv.colors(
	      "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c",
	      "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5",
	      "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f",
	      "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5",
	      "#cc8e33", "#deb87e", "#619e92", "#86b5ab");
      
	  scale.domain.apply(scale, arguments);
	  return scale;
};

function level(occupation) {
	var level = 1;
	if(occupation.subcategory) {
		level = 3;
	} else if(occupation.category) {
		level = 2;
	}
	
	return level;
}

function getValue(stateCode, occupation, measure) {
		var arrVal = stateOccupationData[measure + "_" + level(occupation)];
		
		// get the correct state
		var arrVal = arrVal[stateCode];
		
		// get the correct category
		if (occupation.category) {
			arrVal = arrVal[occupation.category];
		}
		
		// get the correct subcategory
		if (occupation.subcategory) {
			if (arrVal) {
				arrVal = arrVal[occupation.subcategory];
			} else {
				arrVal = -1;
			}
		}
		
		return arrVal;
}

function getAllValues(measure, occupation, stateCode) {
	if (stateCode) {
		return stateOccupationData[measure + "_" + level(occupation)][stateCode];
	} else {
		return stateOccupationData[measure + "_" + level(occupation)];
	}
}

// TODO: don't change globals as side effect of function
function findMaxMinValue(occupation, measure) {
	measureMaxVal = -Infinity;
	measureMinVal = Infinity;
	var levelOccupationData = stateOccupationData[measure + "_" + level(occupation)];
	
	for (var key in levelOccupationData) {
		var arrVal = levelOccupationData[key];
		if (occupation.category) {
			arrVal = arrVal[occupation.category];
		}
		if (occupation.subcategory) {
			if (arrVal) {
				arrVal = arrVal[occupation.subcategory];
			} else {
				arrVal = -1;
			}
		}
		if (arrVal > measureMaxVal && arrVal >= 0) {
			measureMaxVal = arrVal
		}
		if (arrVal < measureMinVal && arrVal >= 0) {
			measureMinVal = arrVal
		}
	}
}

function initializeVars() {
	geoScale = pv.Geo.scale()
			.domain({lng: -128, lat: 24}, {lng: -64, lat: 50})
			.range({x: 0, y: 0}, {x: width, y: geoHeight});
	
	setTitleDescription("geoTitle", selectedMeasure, selectedOccupation);
	findMaxMinValue(selectedOccupation, selectedMeasure);
	
	intervalScale = (measureMaxVal - measureMinVal)/numBoxesInLegend;
	intervalScale = parseFloat(intervalScale.toFixed(2));
	geoColor = pv.Scale.linear(measureMinVal, (measureMinVal + measureMaxVal)/2, measureMaxVal).range("#F7FCB9", "#78C679", "#006837");
}

initializeVars();


// Find the centroid for each state
us_lowres.forEach(function(c) {
	c.code = c.code.toUpperCase();
	c.centLatLon = centroid(c.borders[0]);
});


// Add the main panel
var vis = new pv.Panel()
		.width(width)
		.height(geoHeight + treeHeight + 30)
		.top(0)
		.bottom(0);


// BEGIN GEO MAP

var geoHover = {};

var statePanel,
		stateLabels,
		stateLegend;

// Add a panel for each state
statePanel = vis.add(pv.Panel)
		.data(us_lowres)
		.width(width)
		.height(geoHeight);

// Add a panel for each state land mass
statePanel.add(pv.Panel)
		.data(function(c) { return c.borders })
	.add(pv.Line)
		.data(function(l) { return l })
		.left(geoScale.x)
		.top(geoScale.y)
		.fillStyle(function(d, l, c) {
			return geoColor(getValue(c.code, selectedOccupation, selectedMeasure));
		})
		.lineWidth(1)
		.strokeStyle("#eee")
		.antialias(false)
		.event("mouseover", function(d, l, c) { setVisible('popupData', c, selectedOccupation, true, undefined); geoHover = c; return stateLabels; })
		.event("mouseout", function(d, l, c) { setVisible('popupData', c, selectedOccupation, false, undefined); geoHover = {}; return stateLabels; })
		.event("click", function(d, l, c) {
			selectedState = c;
			redrawMap(false,true);
			return stateLabels;
		});

// Add a label with the state code in the middle of every state
stateLabels = vis.add(pv.Label)
		.data(us_lowres)
		.left(function(c) { return geoScale(c.centLatLon).x })
		.top(function(c) { return geoScale(c.centLatLon).y })
		.text(function(c) { return c.code })
		.textAlign("center")
		.textBaseline("middle")
		.textStyle(function(c) {
			if (selectedState.code == c.code) {
				return "red";
			} else if(geoHover.code == c.code) {
				return "blue";
			} else {
				return "gray";
			}
		})
		.font(function(c) { return (geoHover.code == c.code || selectedState.code == c.code) ? "bold 10px sans-serif" : "10px sans-serif"; });

// Add the color bars for the color legend
stateLegend = vis.add(pv.Bar)
		.data(pv.range(measureMinVal, measureMaxVal, intervalScale).slice(0,numBoxesInLegend))
		.bottom(function(d) { return this.index * 12 + 40 + treeHeight + 30; })
		.height(10)
		.width(10)
		.left(width-120)
		.fillStyle(function(d) { return geoColor(measureMinVal + intervalScale * this.index) })
		.lineWidth(null)
	.anchor("right").add(pv.Label)
		.textAlign("left")
		.text(function(d) { 
			var textRangeMin = d;
			var textRangeMax = (d + intervalScale);
			if (intervalScale < 1) { 
				textRangeMin = textRangeMin.toFixed(2); 
				textRangeMax = textRangeMax.toFixed(2);
			} else {
				textRangeMin = parseInt(textRangeMin); 
				textRangeMax = parseInt(textRangeMax); 
			}
			
			if (selectedMeasure == "total") {
				textRangeMin += '%';
				textRangeMax += '%';
			} else {
				textRangeMin = '$' + textRangeMin;
				textRangeMax = '$' + textRangeMax;
			}
			
			return textRangeMin + " – " + textRangeMax + "";
		});


// BEGIN TREEMAP
setTreemapTitleDescription("treeTitle", selectedMeasure, selectedState);

function getNodes() {
	return nodes;
}

function occupationForNode(node) {
	var occupation = {category:"", subcategory:""};
	
	if (node) {
		if (node.parentNode && node.parentNode.parentNode) {
			occupation.category = node.parentNode.nodeName;
			occupation.subcategory = node.nodeName;
		} else if (node.parentNode) {
			occupation.category = node.nodeName;
		}
	}
		
	return occupation;
}

function occupationsAreEqual(o1, o2) {
	return o1.category == o2.category && o1.subcategory == o2.subcategory;
}

function occupationMatchesNode(occupation, node) {
	return occupationsAreEqual(occupation, occupationForNode(node));
}

var treeHover = {category:"",subcategory:""};

var treeColor = pv.Colors.category24().by(function(d) {
			switch (level(selectedOccupation)) {
				case 1:
				case 2:
					return d.nodeName;
					break;
				case 3:
					return d.parentNode.nodeName;
					break;
			}
		}),
		nodes = pv.dom(getAllValues(selectedMeasure, selectedOccupation, selectedState.code)).nodes();

var treemap = vis.add(pv.Layout.Treemap)
		.width(width)
		.height(treeHeight)
		.top(geoHeight+30)
		.nodes(getNodes)
		.round(false)
		.padding(1);

treemap.leaf.add(pv.Panel)
		.fillStyle(function(d) {
			if (level(occupationForNode(d)) == 3) {
				return occupationMatchesNode(treeHover,d) || occupationMatchesNode(selectedOccupation,d) ? treeColor(d).brighter() : treeColor(d);
			} else {
				return occupationMatchesNode(treeHover,d) || occupationMatchesNode(selectedOccupation,d) ? pv.color("#aec7e8").brighter() : pv.color("#aec7e8");
			}
			})
		.strokeStyle("rgba(255,255,255,0.3)")
		.lineWidth(1)
		.antialias(false)
		.event("mouseover", function(d) { setVisible('popupData', selectedState, occupationForNode(d), true, d); treeHover = occupationForNode(d); return this; })
		.event("mouseout", function(d) { setVisible('popupData', selectedState, occupationForNode(d), false, d); treeHover = occupationForNode(undefined); return this; })
		.event("click", function(d) {
			selectedOccupation = occupationForNode(d);
			redrawMap(true, false);
			return this;
		});

treemap.label.add(pv.Label)
		.visible(function(d) {return level(occupationForNode(d)) == 2; })
		.textAngle(0)
		.textAlign("left")
		.textBaseline("top")
		.left(function(d) { return d.x; })
		.top(function(d) { return d.y; })
		.text(function(d) { return d.nodeName.substring(0,d.dx/8) + "..."; });


// BEGIN redraw code
function redrawMap(geomapChange, treemapChange) {
	if (geomapChange && treemapChange) {
		var selectBox = document.getElementById("measureSelect");
		selectedMeasure = selectBox.options[selectBox.selectedIndex].value;
		selectBox = document.getElementById("categorySelect");
		selectedCategoryGroup = selectBox.options[selectBox.selectedIndex].value;
		if (selectedCategoryGroup == "subcategories") {
			var arrVal = stateOccupationData[selectedMeasure + "_" + 3];
			var arrVal = arrVal[selectedState.code];
			if (selectedOccupation.category) {
				arrVal = arrVal[selectedOccupation.category];
			}
			for (nextSubcategory in arrVal) {
				selectedOccupation.subcategory = nextSubcategory; 
				break;
			}
			
		} else {
			selectedOccupation.subcategory = undefined;
		}
		updateGeomap();
		updateTreemap();
	} else if (geomapChange) {
		updateGeomap();
	} else if (treemapChange) {
		updateTreemap();
	}
}
function updateGeomap() {
	initializeVars();
	
	var newLegendData = pv.range(measureMinVal, measureMaxVal, intervalScale).slice(0,numBoxesInLegend);
	stateLegend.data(newLegendData);
	stateLegend.render();
	statePanel.render();
}

function updateTreemap() {
	setTreemapTitleDescription("treeTitle", selectedMeasure, selectedState);
	nodes = pv.dom(getAllValues(selectedMeasure, selectedOccupation, selectedState.code)).nodes();
	treemap.reset();
	treemap.render();
}

// draw the vis
vis.render();

			</script>
		</div>
		
		<!-- Right panel with options, about, and source info -->
		<div id="rightPanel">
			<h1>Measure</h1>
			<div>
				<select id ="measureSelect" onchange="redrawMap(true, true);">
					<option value="total" selected="selected">Workforce distribution</option>
					<option value="annualMeanWage">Annual mean wage</option>
					<option value="ann10Wage">10th percentile wage</option>
					<option value="ann25Wage">25th percentile wage</option>
					<option value="ann50Wage">50th percentile wage</option>
					<option value="ann75Wage">75th percentile wage</option>
					<option value="ann90Wage">90th percentile wage</option>
				</select>
			</div>
			<h1>Occupational scope</h1>
			<div>
				<select id="categorySelect" onchange="redrawMap(true, true);">
					<option value="categories">Categories</option>
					<option value="subcategories" selected="selected">Sub-Categories</option>
				</select>
			</div>
			<h1>About</h1>
			<div>
				This visualization shows per-state, per-occupation data according to one of seven measures. Click on a state to see a breakdown of the data for all occupations in the treemap, or click on an occupation in the treemap to see how it varies across the nation. States are colored white if an exact value was not reported in the data. The hover text will indicate -1 in this case.
			</div>
			<h1>Source</h1>
			<div>
				This visualization uses the State Cross-Industry estimates of May 2009 Occupational Employment and Wage Estimates data from the U.S. Bureau of Labor Statistics. The original data can be found on the <a href="http://www.bls.gov/oes/oes_dl.htm">BLS website</a>.
			</div>
			<h1>Credits</h1>
			<div>
				Inspiration for this visualization was drawn from the <a href="http://vis.stanford.edu/protovis/ex/treemap.html">treemap</a> and <a href="http://vis.stanford.edu/protovis/ex/choropleth.html">choropleth map</a> examples on the <a href="http://protovis.org/">Protovis website</a>. Additionally, the base colors for the geographic map were generated using the execellent <a href="http://colorbrewer2.org/">Colorbrewer 2.0</a> tool.
			</div>
			<div id="designers">Giannis Neokleous<br />Andrew Hershberger</div>
		</div>
		</div>
	</body>
</html>
